package com.swak.rpc.config.context;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.swak.rpc.config.AbstractConfig;
import com.swak.rpc.config.ServiceConfig;
import com.swak.utils.CollectionUtils;
import com.swak.utils.Maps;
import com.swak.utils.StringUtils;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class ConfigManager implements Framework {

	public static final String NAME = "config";
	private static ConfigManager instance = new ConfigManager();

	public static String getTagName(Class<?> cls) {
		return StringUtils.camelToSplitName(cls.getSimpleName(), "-");
	}

	private Map<String, Map<String, AbstractConfig>> configCache = Maps.newHashMap();
	private final ReadWriteLock lock = new ReentrantReadWriteLock();

	private ConfigManager() {
	}

	public static ConfigManager getInstance() {
		return instance;
	}

	public ConfigManager addConfig(AbstractConfig config) {
		this.write(() -> {
			Map<String, AbstractConfig> configs = configCache.computeIfAbsent(getTagName(config.getClass()),
					(key) -> Maps.newHashMap());
			configs.putIfAbsent(config.getId(), config);
		});
		return this;
	}

	public Collection<ServiceConfig> getServices() {
		return getConfigs(getTagName(ServiceConfig.class));
	}

	protected <C extends AbstractConfig> Collection<C> getConfigs(String configType) {
		return (Collection<C>) read(() -> getConfigsMap(configType).values());
	}

	protected <C extends AbstractConfig> Map<String, C> getConfigsMap(String configType) {
		return (Map<String, C>) read(() -> configCache.getOrDefault(configType, Collections.emptyMap()));
	}

	public void removeConfig(AbstractConfig config) {
		if (config == null) {
			return;
		}

		Map<String, AbstractConfig> configs = configCache.get(getTagName(config.getClass()));
		if (CollectionUtils.isNotEmptyMap(configs)) {
			configs.remove(config.getId());
		}
	}

	private void write(Runnable runnable) {
		Lock writeLock = lock.writeLock();
		writeLock.lock();
		try {
			runnable.run();
		} catch (RuntimeException e) {
			throw e;
		} catch (Throwable e) {
			throw new RuntimeException(e.getCause());
		} finally {
			writeLock.unlock();
		}
	}

	private <V> V read(Callable<V> callable) {
		Lock readLock = lock.readLock();
		V value = null;
		try {
			readLock.lock();
			value = callable.call();
		} catch (Throwable e) {
			throw new RuntimeException(e);
		} finally {
			readLock.unlock();
		}
		return value;
	}
}